﻿///File classes structure
    ///// {=========================ClassName=========================}
            ///-----------------------------Fields-----------------------------
            ///-----------------------------Constructors-----------------------------
            ///-----------------------------Properties-----------------------------
            ///-----------------------------Operators-----------------------------
            ///-----------------------------Methods-----------------------------
            ///-----------------------------InterfaceImplementation------------------


namespace NumericalData
open System 
open System.Collections
open System.Text
open NumericalData.Exceptions

    ///// {=========================Vector=========================}
        [<AllowNullLiteral>]
    type Vector = 
        class
            ///-----------------------------Fields-----------------------------
            val mutable m_vec:double[]

            ///-----------------------------Constructors-----------------------------
            new() = 
                {m_vec = null }

            new(mass:double[]) =
                if mass = null then
                    raise <| VectorConstructorArrayIsNullException
                {m_vec = mass}
            new(vec:Vector) = 
                let newvec:Vector = vec.Clone()
                let newDoubleArray:double[] =  newvec.InnerArray
                {m_vec = newDoubleArray}
            ///-----------------------------Properties-----------------------------
            member x.InnerArray 
                with get() = x.m_vec

            member x.Length 
                with get() = x.m_vec.Length

            ///-----------------------------Operators-----------------------------
            /// Vector,Vector Operators        
            static member (+) ((v1:Vector), (v2:Vector)) = 
                Vector.binOperator (+) (v1,v2)

            static member (-) ((v1:Vector), (v2:Vector)) = 
                Vector.binOperator (-) (v1,v2)

            static member private binOperator operation (v1:Vector,v2:Vector) = 
                if v1.Length <> v2.Length then
                    raise  <| VectorsHaveDifferentLengthsException(v1.Length,v2.Length)
                new Vector(
                    [|
                        for i in 0..(v1.Length-1) do
                            yield (operation v1.[i] v2.[i])
                    |])

            static member (*) ((v1:Vector), (v2:Vector)) = 
                if v1.Length <> v2.Length then
                    raise  <| VectorsHaveDifferentLengthsException(v1.Length,v2.Length)
                v1.InnerArray |>
                Array.mapi (fun index el ->
                    el*v2.[index] ) |>
                Array.sum

            override x.Equals ((v:obj)) = 
                match v with 
                | :? Vector ->
                    let v = v :?> Vector
                    let  rec cmprfunc index = 
                        if index = x.Length - 1 then
                            true
                        else
                            if x.[index] <> v.[index] then
                                false
                            else
                                cmprfunc (index + 1)
                    if x.Length <> v.Length then
                        false
                    else
                        cmprfunc 0
                | _ -> false
         
            override x.GetHashCode() = 
                x.SPrint().GetHashCode()           

            static member (~-) (v:Vector) = 
                let newvec = v.Clone()
                v@/(-1.)
                v
    
            /// Double,Vector Operators
            static member private DoubleOpNew operation (v:Vector) (c:double) = 
                new Vector(
                    [|
                        for i in 0..(v.Length-1) do
                            yield (operation v.[i] c)
                    |])
            static member private DoubleOp operation (v:Vector) (c:double) = 
                for i in 0..(v.Length-1) do
                    v.[i] <- (operation v.[i] c)

            static member (+) ((v1:Vector),(c:double)) = 
                Vector.DoubleOpNew (+) v1 c
            static member (+) ((c:double),(v1:Vector)) = 
                Vector.DoubleOpNew (+) v1 c

            static member (-) ((v1:Vector),(c:double)) = 
                Vector.DoubleOpNew (-) v1 c
            static member (-) ((c:double),(v1:Vector)) = 
                Vector.DoubleOpNew (-) v1 c

            static member (*) ((v1:Vector),(c:double)) = 
                Vector.DoubleOpNew (*) v1 c
            static member (*) ((c:double),(v1:Vector)) = 
                Vector.DoubleOpNew (*) v1 c

            static member (/) ((v:Vector),(c:double)) = 
                if c = 0. then 
                    raise <| VectorDivisionByZeroException
                Vector.DoubleOpNew (/) v c

            static member (@+) ((v1:Vector),(c:double)) = 
                Vector.DoubleOp (+) v1 c
            static member (@+) ((c:double),(v1:Vector)) = 
                Vector.DoubleOp (+) v1 c

            static member (@-) ((v1:Vector),(c:double)) = 
                Vector.DoubleOp (-) v1 c
            static member (@-) ((c:double),(v1:Vector)) = 
                Vector.DoubleOp (-) v1 c

            static member (@*) ((v1:Vector),(c:double)) = 
                Vector.DoubleOp (*) v1 c
            static member (@*) ((c:double),(v1:Vector)) = 
                Vector.DoubleOp (*) v1 c

            static member (@/) ((v:Vector),(c:double)) = 
                if c = 0. then 
                    raise <| VectorDivisionByZeroException
                Vector.DoubleOp (/) v c

            /// Item operator    
            member x.Item 
                with get idx = 
                    if idx >= 0 && idx <= x.Length - 1 then
                        x.m_vec.[idx]
                    else
                        raise <| VectorItemIndexIsOutOfRangeException(idx)
                    
                and  set idx v = 
                    if idx >= 0 && idx <= x.Length - 1 then
                        x.m_vec.[idx] <- v
                    else
                        raise <| VectorItemIndexIsOutOfRangeException(idx)

            ///-----------------------------Methods-----------------------------

            static member FindIndex cmpOp (v:Vector) = 
                let mutable maxIndx = 0
                for i in 1..(v.Length - 1) do
                    if (cmpOp v.[i] v.[maxIndx]) then
                        maxIndx <- i
                maxIndx 

            member x.FindMaxIndex() = 
                Vector.FindIndex (>) x

            member x.FindMinIndex() = 
                Vector.FindIndex (<) x

            member x.ReplaceElements i1 i2 = 
                if i1 >= 0 && i2 >= 0 &&
                    i1 < x.Length && i2 < x.Length then
                    if i1 = i2 then ()
                    else
                        let buf = x.[i1]
                        x.[i1] <- x.[i2]
                        x.[i2] <- buf
                else
                    raise <| VectorReplaceElementsIndexesIsOutOfRangeException(i1,i2) 

            
            member x.ToMatrix() = 
                new Matrix(
                    [|
                        for i in 0..(x.m_vec.Length-1) do
                            yield [|x.m_vec.[i]|]
                    |])
            member x.AsMatrixRow() = 
                new Matrix([| x.Clone().InnerArray |])

            member x.AsMatrixColumn() = 
                new Matrix(
                    [|
                        for i in 0..(x.m_vec.Length-1) do
                            yield [|x.m_vec.[i]|]
                    |])

            static member Generate (dim:int) (avg:double) = 
                let ran = new System.Random()
                let arr:double[] = 
                    [|
                        for i in 1..dim do                            
                            yield  (double)(avg*2.*ran.NextDouble() - avg) 
                    |]
                new Vector(arr)

            member x.SPrint() = 
                (Array.fold (fun acc el -> acc + (sprintf "%f; " el)) "  [| " x.m_vec) + "|]"
                
            member x.Print() = 
                printfn "%s" (x.SPrint())    

            member x.Clone() = 
                (new Vector(((x.m_vec).Clone()):?>(double[])))              
            ///-----------------------------InterfaceImplementation------------------
            interface IEnumerable with 
                member x.GetEnumerator() = x.m_vec.GetEnumerator()
            member x.GetEnumerator() = (x :> IEnumerable).GetEnumerator()
        end
    and
    ///// {=========================Matrix=========================}
    [<AllowNullLiteral>]
    Matrix = 
        class
            ///-----------------------------fields-----------------------------
            val mutable m_vecs: Vector[]
            val mutable m_rows : int
            val mutable m_cols : int
        
            ///-----------------------------Constructors-----------------------------
            new() = 
                { m_vecs= null; m_rows = 0; m_cols = 0}

            new(mass:double[][]) = 
                if(mass = null) then 
                    raise <| MatrixConstructorArrayIsNullException
                if not (Matrix._hasSameDims mass ) then
                    raise <| MatrixConstructorArrayHasDifferentInnerLengthsException
                let vecs = [| for i in 0..(mass.Length - 1) -> new Vector(mass.[i]) |]
                { 
                    m_vecs = vecs
                    m_cols = mass.[0].Length
                    m_rows = mass.Length
                }

            new(matr:Matrix) = 
                let newmatr:Matrix = matr.Clone()
                {
                    m_vecs = newmatr.m_vecs
                    m_cols = newmatr.m_cols
                    m_rows = newmatr.m_rows
                }

            ///-----------------------------Properties-----------------------------
            member public x.InnerArray
                    with get() = [| for i in 0..(x.m_rows - 1) do yield x.m_vecs.[i].InnerArray |]
                    and  set(mass:double[][]) = 
                        if not (Matrix._hasSameDims mass) then
                            raise <| MatrixInnerArraySetterHasDifferentInnerLengthsException
                        else
                            x.m_vecs <- [| for i in 0..(mass.Length - 1) -> new Vector(mass.[i]) |]
                            x.m_cols <- mass.[0].Length
                            x.m_rows <- mass.Length

            member public x.RowsNumber
                with get() = x.m_rows

            member public x.ColumnsNumber
                with get() = x.m_cols

            ///-----------------------------Operators-----------------------------
            static member (*) ((m1:Matrix),(m2:Matrix)) = 
                if m1.ColumnsNumber <> m2.RowsNumber then
                    raise <| MatrixMultiplexionDimentionException(m1.ColumnsNumber,m2.RowsNumber)
                else
                    new Matrix(
                        [|
                            for i = 0 to (m1.RowsNumber - 1) do
                                yield [|
                                    for j = 0 to (m2.ColumnsNumber-1) do
                                        yield m1.GetRowVector(i)*m2.GetColVector(j)
                                |]
                        |])
            static member internal MatrMatrOp  operation ((m1:Matrix),(m2:Matrix)) = 
                if m1.ColumnsNumber <> m2.ColumnsNumber || m1.RowsNumber <> m2.RowsNumber then
                    raise <| MatrixMultiplexionDimentionException(m1.ColumnsNumber,m2.RowsNumber)
                else
                    new Matrix(
                        [|
                            for i = 0 to (m1.RowsNumber - 1) do
                                yield [|
                                    for j = 0 to (m1.ColumnsNumber-1) do
                                        yield operation m1.[i,j] m2.[i,j]
                                |]
                        |])
            static member (+) ((m1:Matrix),(m2:Matrix)) = 
                Matrix.MatrMatrOp (+) ((m1),(m2))
            static member (-) ((m1:Matrix),(m2:Matrix)) = 
                Matrix.MatrMatrOp (-) ((m1),(m2))


            static member internal DoubleOp operation  (m:Matrix) (c:double) = 
                for i in 0..(m.RowsNumber - 1) do
                    for j in 0..(m.ColumnsNumber - 1) do
                        m.[i,j] <- (operation m.[i,j] c)

            static member internal DoubleBinOp operation  (m:Matrix) (c:double) = 
                new Matrix(
                    [|
                        for i in 0..(m.RowsNumber - 1) do
                            yield [|
                                    for j in 0..(m.ColumnsNumber - 1) do
                                        yield (operation m.[i,j] c)
                                  |]
                    |])

            static member (/) ((m:Matrix),(c:double)) = 
                if c = 0. then
                    raise <| MatrixDivisionByZeroException
                Matrix.DoubleBinOp (/) m c
            static member (*) ((m:Matrix),(c:double)) = 
                Matrix.DoubleBinOp (*) m c
            static member (*) ((c:double),(m:Matrix)) = 
                Matrix.DoubleBinOp (*) m c
            //Operators without return value
            static member (@/) ((m:Matrix),(c:double)) = 
                if c = 0. then
                    raise <| MatrixDivisionByZeroException
                Matrix.DoubleOp (/) m c
            static member (@*) ((m:Matrix),(c:double)) = 
                Matrix.DoubleOp (*) m c
            static member (@*) ((c:double),(m:Matrix)) = 
                Matrix.DoubleOp (*) m c


            member x.Item 
                with get  (i,j)  = 
                    if i >= 0 && i <= x.m_rows - 1 &&
                       j >= 0 && j <= x.m_cols - 1 then 
                        x.m_vecs.[i].[j]
                    else
                        raise <| MatrixItemIndexIsOutOfRangeException(i,j)
                and  set (i,j) v  = 
                    if i >= 0 && i <= x.m_rows - 1 &&
                       j >= 0 && j <= x.m_cols - 1 then 
                        x.m_vecs.[i].[j] <- v
                    else
                        raise <| MatrixItemIndexIsOutOfRangeException(i,j)
            member x.Item
                with get(i) = 
                    if i >= 0 && i <= x.m_rows - 1 then
                        x.m_vecs.[i]
                    else
                        raise <| MatrixItemIndexIsOutOfRangeException(i,0)
                and  set (i) (v:Vector)  = 
                    if i >= 0 && i <= x.m_rows - 1  then 
                        x.m_vecs.[i] <- v
                    else
                        raise <| MatrixItemIndexIsOutOfRangeException(i,0) 
                    

            ///-----------------------------Methods-----------------------------

            member x.TransponMatrix() = 
                new Matrix(
                    [|
                        for i in 0..(x.RowsNumber - 1) do
                            yield [|
                                for j in 0..(x.ColumnsNumber - 1) do
                                    yield x.[j,i]
                            |]
                    |])

            member x.ToSquareMatrix() = 
                if x.ColumnsNumber <> x.RowsNumber then
                    raise <| MatrixSquareMatrixDimentionCastException(x.ColumnsNumber, x.RowsNumber)
                else
                    new SquareMatrix(x.InnerArray)

            member x.ToVector() = 
                if x.m_cols <> 1 then
                    raise <| MatrixToVectorDimentionsException(x.m_cols)
                else
                    new Vector(
                        [|
                            for i in 0..(x.m_rows - 1) do
                                yield x.m_vecs.[i].[0]
                        |])

            member x.GetRowVector(rowIndex) = 
                if rowIndex >= 0 && rowIndex <= (x.m_rows - 1) then
                    x.m_vecs.[rowIndex].Clone()
                else
                    raise <| MatrixRowIndexIsOutOfRangeException("GetRowVector",rowIndex)

            member x.GetColVector(colIndex) =
                if colIndex >= 0 && colIndex <= x.m_cols - 1 then
                    new Vector(
                        [| for i in 0..(x.m_rows - 1)  do yield x.m_vecs.[i].[colIndex] |]
                    )
                else
                    raise <| MatrixRowIndexIsOutOfRangeException("GetColVector",colIndex)

            member x.ReplaceRows i1 i2 = 
                if i1 >= 0 && i2 >= 0 && 
                    i1 <= (x.m_rows - 1) && i2 <= (x.m_rows - 1) then 
                    if i1 = i2 then
                        ()
                    else
                        let buf = x.m_vecs.[i1]
                        x.m_vecs.[i1] <- x.m_vecs.[i2]
                        x.m_vecs.[i2] <- buf
                else
                    raise <| MatrixReplaceRowsIndexesIsOutOfRangeException(i1,i2)

            member x.ReplaceCols i1 i2 = 
                if i1 >= 0 && i2 >= 0 && 
                    i1 <= (x.m_cols - 1) && i2 <= (x.m_cols - 1) then 
                    if i1 = i2 then
                        ()
                    else
                        for i in 0..(x.m_rows - 1) do 
                            let buf = x.[i,i1]
                            x.[i,i1] <- x.[i,i2]
                            x.[i,i2] <- buf
                else
                    raise <| MatrixReplaceColsIndexesIsOutOfRangeException(i1,i2)
            ///Rows and Columns change methods
            member private x.ChangeColumn (operation:double->double->double) (columnindex) (value:double) = 
                if columnindex >= 0 && columnindex <= (x.m_cols - 1)  then 
                    for i in 0..(x.m_rows - 1) do 
                            x.[i,columnindex] <- operation x.[i,columnindex] value
                else
                    raise <| MatrixColumnIndexIsOutOfRangeException("ChangeColumn",columnindex)
            
            member private x.ChangeRow operation (rowindex) (value:double) = 
                if rowindex >= 0 && rowindex <= (x.m_rows - 1)  then 
                    operation x.m_vecs.[rowindex] value
                else
                    raise <| MatrixRowIndexIsOutOfRangeException("ChangeRow",rowindex)

            member x.AddToColumn (colIndex:int) (v:double) = 
                x.ChangeColumn (+) colIndex v
            member x.SubtractFromColumn (colIndex:int) (v:double) = 
                x.ChangeColumn (-) colIndex v
            member x.MultiplyColumn (colIndex:int) (v:double) = 
                x.ChangeColumn (*) colIndex v
            member x.DivideColumn (colIndex:int) (v:double) = 
                if v = 0. then
                    raise <| MatrixColumnDivisionByZeroException
                else
                    x.ChangeColumn (/) colIndex v

            member x.AddToRow (rowIndex:int) (v:double) = 
                x.ChangeRow (fun row v -> row@+v) rowIndex v
            member x.SubtractFromRow (rowIndex:int) (v:double) = 
                x.ChangeRow (fun row v -> row@-v) rowIndex v
            member x.MultiplyRow (rowIndex:int) (v:double) = 
                x.ChangeRow (fun row v -> row@*v) rowIndex v
            member x.DivideRow (rowIndex:int) (v:double) = 
                if v = 0. then
                    raise <| MatrixRowDivisionByZeroException
                else
                    x.ChangeRow (fun row v -> row@/v) rowIndex v
                                            
            member x.SPrint() = 
                let mutable out = new StringBuilder()
                sprintf "  [|" |> out.AppendLine |> ignore
                for i in 0..(x.m_rows - 1) do
                    sprintf "    [|" |> out.Append |> ignore
                    for j in 0..(x.m_cols - 1) do
                        sprintf "   %f;" x.[i,j] |> out.Append |> ignore
                    sprintf "|]" |> out.AppendLine |> ignore
                sprintf "  |]" |> out.AppendLine |> ignore
                out.ToString()

            member x.Print() = 
                printf "%s" (x.SPrint())

            static member internal _hasSameDims (mass:double[][]) = 
                let dim1 = mass.[0].Length
                try
                    mass |>
                    Array.iter (fun str ->
                        if str.Length <> dim1 then
                            failwith "break"
                        )
                    true
                with 
                |_ ->
                    false
                    
            member x.Clone() =  
                new Matrix(
                    [|
                        for vec in (x.m_vecs) -> vec.Clone().InnerArray
                    |])
            ///-----------------------------InterfaceImplementation------------------
            interface IEnumerable with 
                    member x.GetEnumerator() = (x.m_vecs).GetEnumerator()
            //member x.GetEnumerator () = (x :> IEnumerable).GetEnumerator()
        end
    and

    ///// {=========================SquareMatrix=========================}
    [<AllowNullLiteral>]
    SquareMatrix = 
        class
            inherit Matrix
            ///-----------------------------Fields-----------------------------

            ///-----------------------------Constructors-----------------------------
            new() = 
                {
                    inherit Matrix
                }

            new(mass:double[][]) = 
                if not (SquareMatrix._isSquareArray mass) then
                    raise <| SquareMatrixConstructorArrayDimentionsException
                {
                    inherit Matrix(mass)
                }

            new(matr:SquareMatrix) = 
                let newmatr = (matr:>Matrix).Clone()
                {
                    inherit Matrix(newmatr)
                }
            ///-----------------------------Properties-----------------------------
            member public x.Dimention
                with get() = x.RowsNumber

            member public x.Determinant
                with get() = 
                    let rec det (mass:double[][]) = 
                        if mass.Length = 1 then
                            mass.[0].[0] 
                        else
                            mass.[0] |>
                            Array.Parallel.mapi (fun strIndex el -> 
                                let truncated = x.StringMinor mass strIndex
                                if strIndex%2 = 0 then
                                    el*(det truncated)
                                else
                                    (-el)*(det truncated)
                                )|>
                            Array.sum
                    det x.InnerArray

            member public x.DiagonalVector 
                with get() = 
                    new Vector(
                        [|
                            for i in 0..(x.Dimention - 1) -> x.[i,i]
                        |])
            ///-----------------------------Operators-----------------------------
            static member (*) ((m1:SquareMatrix),(m2:SquareMatrix)) = 
                Matrix.op_Multiply(m1,m2).ToSquareMatrix()

            static member internal MatrMatrOp  operation ((m1:SquareMatrix),(m2:SquareMatrix)) = 
                (Matrix.MatrMatrOp operation (m1,m2)).ToSquareMatrix()

            static member (+) ((m1:SquareMatrix),(m2:SquareMatrix)) = 
                (Matrix.MatrMatrOp (+) (m1,m2) ).ToSquareMatrix()

            static member (-) ((m1:SquareMatrix),(m2:SquareMatrix)) = 
                (Matrix.MatrMatrOp (-) (m1,m2) ).ToSquareMatrix()


            static member (/) ((m:SquareMatrix),(c:double)) = 
                if c = 0. then
                    raise <| MatrixDivisionByZeroException
                (Matrix.DoubleBinOp (/) m c ).ToSquareMatrix()
            static member (*) ((m:SquareMatrix),(c:double)) = 
                (Matrix.DoubleBinOp (*) m c ).ToSquareMatrix()
            static member (*) ((c:double),(m:SquareMatrix)) = 
                (Matrix.DoubleBinOp (*) m c ).ToSquareMatrix()
            //Operators without return value
            static member (@/) ((m:SquareMatrix),(c:double)) = 
                if c = 0. then
                    raise <| MatrixDivisionByZeroException
                (Matrix.DoubleOp (/) m c )
            static member (@*) ((m:SquareMatrix),(c:double)) = 
                Matrix.DoubleOp (*) m c
            static member (@*) ((c:double),(m:SquareMatrix)) = 
                Matrix.DoubleOp (*) m c


            ///-----------------------------Methods-----------------------------
            member x.Clone() = 
                new SquareMatrix((x:>Matrix).Clone().InnerArray)

            member x.ToMatrix() = 
                new Matrix(x.InnerArray)

            member private x.StringMinor (mass:double[][]) (strIndex:int) = 
                [|
                    for i in 1..(mass.Length-1) do
                        let left = mass.[i].[0..(strIndex-1)]
                        let right = mass.[i].[(strIndex+1)..(mass.Length-1)]
                        yield Array.concat [|left;right |]
                |]

            static member GetMatrixWithInversedDiagonal (matr:SquareMatrix) = 
                let diagonalMultiplexion = 
                    matr.DiagonalVector.InnerArray |>
                    Array.fold (fun acc el -> acc*el) 1.
                if diagonalMultiplexion = 0. then
                    raise <| SquareMatrixDiagonalMatrixDivisionByZeroException
                let newMatr:SquareMatrix = matr.Clone()
                for i in 0..(newMatr.Dimention - 1) do
                    newMatr.[i,i] <- 1. / newMatr.[i,i] 
                newMatr
                    
            ///Common MatrixGenerator Method

            static member GetMatrixAsDiagonal(matr:SquareMatrix) = 
                new SquareMatrix(
                    [|
                        for i in 0..(matr.Dimention - 1) do
                            yield [| 
                                    for j in 0..(matr.Dimention - 1) do
                                        if i = j then
                                            yield matr.[i,j]
                                        else
                                            yield 0.
                                  |]
                    |])

            static member private _isSquareArray (mass:double[][]) = 
                if (mass.[0].Length = mass.Length) && (Matrix._hasSameDims mass) then
                    true
                else 
                    false

        end